package com.sikaryofficial.backend.manager.information;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sikaryofficial.backend.annotation.TenantFilter;
import com.sikaryofficial.backend.config.ArticleConfig;
import com.sikaryofficial.backend.config.PointsRuleConfig;
import com.sikaryofficial.backend.config.TenantFilterProperties;
import com.sikaryofficial.backend.constant.ArticleAuditStatusEnum;
import com.sikaryofficial.backend.constant.ArticleContentTypeEnum;
import com.sikaryofficial.backend.constant.ArticleLabelEnum;
import com.sikaryofficial.backend.constant.ArticleSourceEnum;
import com.sikaryofficial.backend.constant.ArticleStatusEnum;
import com.sikaryofficial.backend.constant.GuideTaskEnum;
import com.sikaryofficial.backend.constant.MyStatisticsTypeEnum;
import com.sikaryofficial.backend.constant.ReportConstant;
import com.sikaryofficial.backend.constant.SearchBusinessEnum;
import com.sikaryofficial.backend.constant.TagStatus;
import com.sikaryofficial.backend.domain.dto.req.information.ArticleAssociateListReq;
import com.sikaryofficial.backend.domain.dto.req.information.ArticleAuditListReq;
import com.sikaryofficial.backend.domain.dto.req.information.ArticleAuditReq;
import com.sikaryofficial.backend.domain.dto.req.information.ArticleBackendSaveReq;
import com.sikaryofficial.backend.domain.dto.req.information.ArticleBrandSpecialListReq;
import com.sikaryofficial.backend.domain.dto.req.information.ArticleOwnerListReq;
import com.sikaryofficial.backend.domain.dto.req.information.ArticleReq;
import com.sikaryofficial.backend.domain.dto.req.information.GetLikesListReq;
import com.sikaryofficial.backend.domain.dto.req.points.GuideTaskReq;
import com.sikaryofficial.backend.domain.dto.resp.information.ArticleExtDTO;
import com.sikaryofficial.backend.domain.dto.resp.information.ArticleGetLikesDTO;
import com.sikaryofficial.backend.domain.entity.Article;
import com.sikaryofficial.backend.domain.entity.ArticleAuditLog;
import com.sikaryofficial.backend.domain.entity.ArticleCategoryEntity;
import com.sikaryofficial.backend.domain.entity.ArticleExposureCount;
import com.sikaryofficial.backend.domain.entity.ArticleExt;
import com.sikaryofficial.backend.domain.entity.ArticleTag;
import com.sikaryofficial.backend.domain.entity.UserReport;
import com.sikaryofficial.backend.domain.mapping.ArticleExtMapping;
import com.sikaryofficial.backend.domain.mapping.ArticleMapping;
import com.sikaryofficial.backend.domain.vo.CountryAreaVO;
import com.sikaryofficial.backend.service.IArticleAuditLogService;
import com.sikaryofficial.backend.service.IArticleCategoryMappingService;
import com.sikaryofficial.backend.service.IArticleExposureCountService;
import com.sikaryofficial.backend.service.IArticleExtService;
import com.sikaryofficial.backend.service.IArticleService;
import com.sikaryofficial.backend.service.IArticleTagService;
import com.sikaryofficial.backend.service.IArticleViewsService;
import com.sikaryofficial.backend.service.IAttachmentService;
import com.sikaryofficial.backend.service.IPointsDetailStrategy;
import com.sikaryofficial.backend.service.IStoreService;
import com.sikaryofficial.backend.service.IUserReportService;
import com.sikaryofficial.backend.service.address.Ip2LocationService;
import com.sikaryofficial.backend.service.article.cache.ArticleActionCacheService;
import com.sikaryofficial.backend.service.article.cache.ArticleCacheService;
import com.sikaryofficial.backend.service.article.cache.ArticleCategoryCacheService;
import com.sikaryofficial.backend.service.article.cache.ArticleLogoCacheService;
import com.sikaryofficial.backend.service.article.event.ArticleCountEvent;
import com.sikaryofficial.backend.service.article.event.AuditEvent;
import com.sikaryofficial.backend.service.guidetask.GuideTaskProcess;
import com.sikaryofficial.backend.service.points.cache.PointsRuleConfigCacheService;
import com.sikaryofficial.backend.service.statistics.MyStatisticsEvent;
import com.sikaryofficial.backend.service.statistics.MyStatisticsProcessor;
import com.sikaryofficial.backend.service.user.UserCacheService;
import com.sikaryofficial.backend.service.useraction.cache.UserActionCacheService;
import com.sikaryofficial.common.core.constant.CacheBusinessConstants;
import com.sikaryofficial.common.core.constant.PointRuleConstant;
import com.sikaryofficial.common.core.domain.R;
import com.sikaryofficial.common.core.exception.ServiceException;
import com.sikaryofficial.common.core.utils.SpringUtils;
import com.sikaryofficial.common.core.web.domain.PageResult;
import com.sikaryofficial.common.redis.service.RedisService;
import com.sikaryofficial.common.security.utils.SecurityUtils;
import com.sikaryofficial.system.api.RemoteArticleService;
import com.sikaryofficial.system.api.model.UserPointResultVO;
import com.sikaryofficial.system.api.model.att.AttachmentResp;
import com.sikaryofficial.system.api.model.infomation.ArticleAssociateResp;
import com.sikaryofficial.system.api.model.infomation.ArticleCategoryDTO;
import com.sikaryofficial.system.api.model.infomation.ArticleCategoryObjVO;
import com.sikaryofficial.system.api.model.infomation.ArticleDTO;
import com.sikaryofficial.system.api.model.infomation.ArticleListReq;
import com.sikaryofficial.system.api.model.infomation.KolListReq;
import com.sikaryofficial.system.api.model.infomation.StatuteListReq;
import com.sikaryofficial.system.api.model.infomation.TopicObjVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.compress.utils.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.sikaryofficial.backend.constant.ContentConstant.CONTENT_STATUS_REPORT;

/**
 * @author : qinjinyuan
 * @desc :  文章数据同步管理类
 * @date : 2024/03/13 11:50
 */
@Component
@Slf4j
@SuppressWarnings(value = "unchecked")
public class ArticleFrontEndManager {
    @Autowired
    private RemoteArticleService remoteArticleService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private UserActionCacheService userActionCacheService;
    @Autowired
    private UserCacheService userCacheService;
    @Autowired
    private IArticleService articleService;
    @Autowired
    private TenantFilterProperties tenantFilterProperties;
    @Autowired
    private IUserReportService reportService;
    @Autowired
    private IAttachmentService attachmentService;
    @Autowired
    private IArticleAuditLogService auditLogService;
    @Autowired
    private IArticleViewsService articleViewsService;
    @Autowired
    private MyStatisticsProcessor myListener;
    @Autowired
    private IArticleExtService articleExtService;
    @Autowired
    private IArticleCategoryMappingService articleCategoryMappingService;
    @Autowired
    private IArticleExposureCountService exposureCountService;
    @Autowired
    private ArticleCategoryCacheService categoryCacheService;
    @Autowired
    private ArticleLogoCacheService logoCacheService;
    @Autowired
    private ArticleCacheService articleCacheService;
    @Autowired
    private ArticleActionDataManager actionDataManager;
    @Autowired
    private Ip2LocationService ip2LocationService;
    @Autowired
    private ArticleActionCacheService articleActionCacheService;
    @Resource
    private PointsRuleConfig pointsRuleConfig;
    @Resource
    private Map<String, IPointsDetailStrategy> pointsDetailStrategyMap;
    @Resource
    private ApplicationEventPublisher publisher;
    @Resource
    private ArticleConfig articleConfig;
    @Autowired
    private Ip2LocationService ip2Location;
    @Autowired
    private GuideTaskProcess guideTaskProcess;
    @Autowired
    private IArticleTagService articleTagService;
    @Resource
    private PointsRuleConfigCacheService ruleConfigCacheService;

    private void commonRemovePreprocessing(String articleIds, Consumer<List<Article>> additionalProcessing) {
        String[] ids = articleIds.split(",");
        List<Long> articleIdList = Lists.newArrayList();
        for (String id : ids) {
            articleIdList.add(Long.valueOf(id));
        }
        if (articleIdList.size() > 10) {
            throw new ServiceException("delete id size is bigger than 10");
        }
        List<Article> list = articleService.listByIds(articleIdList);
        for (Article article : list) {
            // 爬虫数据不能删除
            if (ArticleSourceEnum.CRAWLER.getCode().equals(article.getSource())) {
                throw new ServiceException("crawler data could not process");
            }
        }
        additionalProcessing.accept(list); // 执行额外的处理逻辑
        clearCacheSet(articleIds);
    }

    /**
     * 文章删除
     *
     * @param articleIds
     * @return
     */
    public void remove(String articleIds) {
        commonRemovePreprocessing(articleIds, list -> {
            remoteArticleService.remove(articleIds);
            for (Article article : list) {
                String brandNo = tenantFilterProperties.getBrandByTenantId(article.getTenantId());
                publisher.publishEvent(new ArticleCountEvent(brandNo, article.getContentType(), article.getSource()));
            }
            // 清除其他缓存逻辑...
            // 清除缓存
            articleCacheService.clearCache();
            Set<Long> createdBys = list.stream().map(Article::getCreatedBy).filter(Objects::nonNull).collect(Collectors.toSet());
            for (Long createdBy : createdBys) {
                articleCacheService.clearOwnerCache(createdBy);
            }
            // 加载统计数据缓存
            articleCacheService.loadArticleStatistics();
        });
    }

    public void removeOwner(String articleIds) {
        commonRemovePreprocessing(articleIds, list -> {
            for (Article article : list) {
                // 只能删除自己的文章
                if (!SecurityUtils.getUserId().equals(article.getCreatedBy())) {
                    throw new ServiceException("only owner can be deleted");
                }
            }
            remoteArticleService.removeOwner(articleIds);
            // 无需返回值处理，因为此方法原定义为void
        });
    }

    private void clearCacheSet(String articleIds) {
        if (CharSequenceUtil.isNotBlank(articleIds)) {
            String[] articleStrIds = articleIds.split(",");
            redisService.removeItemCacheZSet(CacheBusinessConstants.ARTICLE_REVIEW_TOP10_CACHE, articleStrIds);
        }
    }

    /**
     * 文章保存
     *
     * @param req
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public UserPointResultVO saveArticle(ArticleReq req) {
        UserPointResultVO pointResultVO = UserPointResultVO.builder().getPointsFlag(false).pointsDetailValue(0).build();
        // 检查是否编辑自己的文章
        checkOwnerArticle(req);
        Article saveData = buildParam(req);
        // 构建国家区域信息
        buildCountryAreaInfo(saveData);
        ArticleExtDTO extData = ArticleExtMapping.INSTANCE.coverToEntity(req);
        // 处理保存文章数据
        processSaveData(saveData, extData, req.isHasInsert(), req.getAttachmentList());
        // 1、添加文章统计
        processStatisticsData(saveData.getContentType(), saveData.getArticleId(), SecurityUtils.getUserId());
        // 2、添加文章topic 检查
        SpringUtils.getBean(ArticleFrontEndManager.class).topicCheck(extData.getTopicList(), saveData);
        // 每日首页发布文章，积分处理
        processPoints(saveData, req.getTopicList(), pointResultVO);
        // 任意的story/reels/post发布可以触发完成指引任务
        if (Objects.nonNull(req.getHasGuideTask()) && Boolean.TRUE.equals(req.getHasGuideTask())) {
            String taskCode = GuideTaskEnum.HOME_PAGE_POST.getTaskCode();
            GuideTaskReq guideTaskReq = new GuideTaskReq(taskCode, SecurityUtils.getUserId());
            guideTaskProcess.processGuideTask(guideTaskReq);
        }
        // 设值主键值
        pointResultVO.setObjectId(saveData.getArticleId());
        return pointResultVO;
    }

    private void buildCountryAreaInfo(Article saveData) {
        // 追加国家区域城市
        CountryAreaVO countryAreaVO = ip2LocationService.getCountryAreaVO();
        if (Objects.nonNull(countryAreaVO)) {
            saveData.setCity(countryAreaVO.getCity());
            saveData.setCountry(countryAreaVO.getCountryName());
            saveData.setArea(countryAreaVO.getArea());
        }
    }

    private void checkOwnerArticle(ArticleReq req) {
        if (Objects.isNull(req.getArticleId())) {
            return;
        }
        boolean result =
                articleService.count(new LambdaQueryWrapper<Article>()
                        .eq(Article::getArticleId, req.getArticleId())
                        .eq(Article::getDeletedVersion, 0L)
                        .eq(Article::getCreatedBy, SecurityUtils.getUserId())
                ) > 0;
        if (!result) {
            throw new ServiceException("only update your owner article");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @TenantFilter
    public Long saveBackendArticle(ArticleBackendSaveReq req) {
        // 只有IN_REVIEW状态的文章才可进行审核
        if (ArticleAuditStatusEnum.APPROVED.getCode().equals(req.getAuditStatus())
                || ArticleAuditStatusEnum.REVIEW_FAILED.getCode().equals(req.getAuditStatus())) {
            throw new ServiceException("Approved article do not process");
        }
        if (ArticleSourceEnum.USER.getCode().equals(req.getSource())
                || ArticleSourceEnum.CRAWLER.getCode().equals(req.getSource())) {
            throw new ServiceException("only update your owner article");
        }
        Article saveData = buildParam(req);
        ArticleExtDTO extData = ArticleExtMapping.INSTANCE.coverBackendToEntity(req);

        // 处理保存文章数据
        processSaveData(saveData, extData, req.isHasInsert(), req.getAttachmentList());
        // 1、添加文章统计
        processStatisticsData(saveData.getContentType(), saveData.getArticleId(), SecurityUtils.getUserId());
        // 2、添加文章topic 检查
        SpringUtils.getBean(ArticleFrontEndManager.class).topicCheck(extData.getTopicList(), saveData);
        return saveData.getArticleId();
    }

    private void processSaveData(Article saveData, ArticleExtDTO extData, boolean hasInsert, List<AttachmentResp> attachmentList) {
        // 3、发布时间设置
        if (ArticleAuditStatusEnum.IN_REVIEW.getCode().equals(saveData.getAuditStatus())) {
            saveData.setPublishTime(new Date());
        }
        // 4、文章信息保存
        if (CollUtil.isNotEmpty(extData.getTopicList())) {
            saveData.setArticleParentId(extData.getTopicList().get(0).getTopicId());
        }
        articleService.saveOrUpdate(saveData);
        // 5、文章扩展信息保存
        saveArticleExtInfo(extData, saveData.getArticleId(), SecurityUtils.getUserId());
        // 6、保存分类(子标题)信息
        saveArticleCategoryMapper(saveData.getArticleId(), extData.getArticleCategoryList());
        // 文章附件信息保存
        saveAttachment(attachmentList, saveData.getArticleId());
    }

    /**
     * 文章附件信息保存
     *
     * @param attachmentList
     * @param articleId
     */
    private void saveAttachment(List<AttachmentResp> attachmentList, Long articleId) {
        // 删除原有附件
        attachmentService.deleteAttachmentList(articleId, SecurityUtils.getUserId());
        // 保存附件信息
        attachmentService.saveAttachmentList(articleId, attachmentList, SecurityUtils.getUserId());
    }

    private Article buildParam(ArticleBackendSaveReq req) {
        Long articleId = req.getArticleId();
        Long userId = SecurityUtils.getUserId();
        req.setHasInsert(true);
        if (Objects.isNull(req.getArticleId())) {
            articleId = IdWorker.getId();
            req.setArticleId(articleId);
            req.setCreatedBy(userId);
            req.setCreatedName(SecurityUtils.getUsername());
        } else {
            req.setHasInsert(false);
            req.setUpdatedBy(SecurityUtils.getUserId());
            req.setUpdatedTime(new Date());
        }
        req.setArticleId(articleId);
        return ArticleMapping.INSTANCE.coverBackendToEntity(req);
    }

    private Article buildParam(ArticleReq req) {
        Long articleId = req.getArticleId();
        Long userId = SecurityUtils.getUserId();
        req.setHasInsert(true);
        if (Objects.isNull(req.getArticleId())) {
            articleId = IdWorker.getId();
            req.setArticleId(articleId);
            req.setCreatedBy(userId);
            req.setCreatedName(SecurityUtils.getUsername());
        } else {
            req.setHasInsert(false);
            req.setUpdatedBy(SecurityUtils.getUserId());
            req.setUpdatedTime(new Date());
        }
        req.setArticleId(articleId);
        return ArticleMapping.INSTANCE.coverToEntity(req);
    }

    @TenantFilter
    public void topicCheck(List<TopicObjVO> topicList, Article saveData) {
        if (CollUtil.isEmpty(topicList)) {
            return;
        }
        if (topicList.size() != 1) {
            throw new ServiceException("only add one topic");
        }
        if (Objects.isNull(topicList.get(0))) {
            log.error("topic is null");
            return;
        }
        Long topicId = topicList.get(0).getTopicId();
        saveData.setArticleParentId(topicId);
        Article topicEntity = articleService.getOne(new LambdaQueryWrapper<Article>()
                .select(Article::getArticleId)
                .eq(Article::getArticleId, topicId)
                .eq(Article::getArticleParentId, 0L)
                .eq(Article::getDeletedVersion, 0L)
        );
        if (Objects.isNull(topicEntity)) {
            throw new ServiceException("the topic not exist");
        }
    }

    private void saveArticleCategoryMapper(Long articleId, List<ArticleCategoryObjVO> articleCategoryList) {
        if (CollUtil.isEmpty(articleCategoryList)) {
            return;
        }
        articleCategoryMappingService.update(new LambdaUpdateWrapper<ArticleCategoryEntity>()
                .setSql(" deleted_version = article_category_mapper_id ")
                .set(ArticleCategoryEntity::getUpdatedBy, SecurityUtils.getUserId())
                .set(ArticleCategoryEntity::getUpdatedTime, new Date())
                .eq(ArticleCategoryEntity::getArticleId, articleId)
                .eq(ArticleCategoryEntity::getDeletedVersion, 0L)
        );
        List<ArticleCategoryEntity> insertDataList = Lists.newArrayList();
        for (ArticleCategoryObjVO categoryObjVO : articleCategoryList) {
            ArticleCategoryEntity categoryEntity = ArticleCategoryEntity.builder()
                    .articleCategoryMapperId(IdWorker.getId())
                    .articleId(articleId)
                    .categoryId(categoryObjVO.getArticleCategoryId())
                    .createdBy(SecurityUtils.getUserId())
                    .build();
            insertDataList.add(categoryEntity);
        }
        articleCategoryMappingService.saveBatch(insertDataList);
    }

    /**
     * 保存文章扩展信息
     *
     * @param articleId
     * @param userId
     */
    private void saveArticleExtInfo(ArticleExtDTO extData, Long articleId, Long userId) {
        // 保存数据前，对数据进行字段补充
        Map<Long, ArticleCategoryDTO> categoryMap = categoryCacheService.categoryMap();
        if (CollUtil.isNotEmpty(extData.getArticleCategoryList())) {
            extData.getArticleCategoryList().stream().filter(Objects::nonNull).forEach(item -> {
                ArticleCategoryDTO articleCategoryDTO = MapUtils.getObject(categoryMap, item.getArticleCategoryId());
                if (Objects.nonNull(articleCategoryDTO)) {
                    item.setCover(articleCategoryDTO.getCover());
                    item.setArticleCategoryName(articleCategoryDTO.getArticleCategoryName());
                }
            });
        }
        ArticleExt ext = ArticleExt.builder()
                .articleId(articleId)
                .topicList(extData.getTopicList())
                .friendList(extData.getFriendList())
                .articleCategoryList(extData.getArticleCategoryList())
                .createdBy(userId)
                .createdName(SecurityUtils.getUsername())
                .build();
        articleExtService.saveOrUpdate(ext);
    }

    /**
     * 处理文章统计数据
     *
     * @param contentType
     * @param articleId
     * @param userId
     */
    @TenantFilter
    private void processStatisticsData(Integer contentType, Long articleId, Long userId) {
        ArticleContentTypeEnum contentTypeEnum = ArticleContentTypeEnum.getNameByCode(contentType);
        switch (contentTypeEnum) {
            case DYNAMIC:
                myListener.addMyStatistics(new MyStatisticsEvent(MyStatisticsTypeEnum.ARTICLE_DYNAMIC_NUM.getBusinessType(), userId, articleId));
                break;
            case LONG_ESSAY:
                myListener.addMyStatistics(new MyStatisticsEvent(MyStatisticsTypeEnum.ARTICLE_LONG_NUM.getBusinessType(), userId, articleId));
                break;
            case SHORT_VIDEO:
                myListener.addMyStatistics(new MyStatisticsEvent(MyStatisticsTypeEnum.ARTICLE_SHORT_VIDEO_NUM.getBusinessType(), userId, articleId));
                break;
            default:
                break;
        }
    }

    /**
     * 文章详情
     *
     * @param articleId
     * @return
     */
    public ArticleDTO getInfo(Long articleId) {
        R<ArticleDTO> result = remoteArticleService.getInfo(articleId);
        if (Objects.isNull(result) || Objects.isNull(result.getData())) {
            throw new ServiceException("Article is not exist.");
        }
        ArticleDTO articleDTO = result.getData();
        // zset 数据结构  文章ID  count = count + 1
        redisService.incrementCacheZSet(CacheBusinessConstants.ARTICLE_REVIEW_TOP10_CACHE, articleId.toString(), 1);
        // 处理附加信息
        List<ArticleDTO> list = Lists.newArrayList();
        list.add(articleDTO);
        actionDataManager.buildUserActionData(list, SecurityUtils.getUserId());
        ArticleDTO articleDTO1 = list.iterator().next();
        // 获取扩展信息
        buildInfoExt(articleId, articleDTO1);
        // 浏览并点赞官方发布的带Event字样的帖子
        boolean isBackend = pointsRuleConfig.isOfficialUser(articleDTO.getCreatedBy())
                && Objects.equals(ArticleLabelEnum.EVENT.getCode(), articleDTO.getArticleLabel())
                && articleActionCacheService.hasUserLiked(SecurityUtils.getUserId(), articleId)
                && articleActionCacheService.hasNoUserViewed(SecurityUtils.getUserId(), articleId);
        articleActionCacheService.recordUserView(SecurityUtils.getUserId(), articleId);
        articleDTO1.setGetPointsFlag(false);
        if (isBackend) {
            // 发帖带指定topic
            IPointsDetailStrategy pointsDetailStrategy = pointsDetailStrategyMap.get(PointRuleConstant.ARTICLE_VIEW_LIKE + IPointsDetailStrategy.SUFFIX);
            boolean pointResult = pointsDetailStrategy.updatePoints(articleDTO1.getArticleId(), SecurityUtils.getUserId());
            articleActionCacheService.clearUserViewLikeCache(SecurityUtils.getUserId(), articleId);
            if (Boolean.TRUE.equals(pointResult)) {
                articleDTO1.setPointsDetailValue(ruleConfigCacheService.getConfigByCode(PointRuleConstant.ARTICLE_VIEW_LIKE).getPointsValue());
                articleDTO1.setGetPointsFlag(true);
            }
        }
        // 文章logo
        articleDTO1.setLogoUrl(logoCacheService.getLogoUrlBySite(articleDTO1.getArticleSite()));
        // 查询统计数据
        buildViewCount(articleDTO1);
        // 移除附件数据 （用户发送长文）
        if (ArticleContentTypeEnum.LONG_ESSAY.getCode().equals(articleDTO1.getContentType())
                && ArticleSourceEnum.USER.getCode().equals(articleDTO1.getSource())) {
            articleDTO1.setAttachmentList(Lists.newArrayList());
            articleDTO1.setCoverUrlList(Lists.newArrayList());
        }
        // 设置品牌字段
        articleDTO1.setBrandNo(tenantFilterProperties.getBrandByTenantId(articleDTO1.getTenantId()));
        return articleDTO1;
    }

    private void buildInfoExt(Long articleId, ArticleDTO articleDTO1) {
        ArticleExt articleExt = articleExtService.getVOById(articleId);
        if (Objects.nonNull(articleExt)) {
            articleDTO1.setFriendList(articleExt.getFriendList());
            articleDTO1.setTopicList(articleExt.getTopicList());
            if (CollUtil.isNotEmpty(articleExt.getArticleCategoryList())) {
                Map<Long, ArticleCategoryDTO> categoryMap = categoryCacheService.categoryMap();
                articleExt.getArticleCategoryList().forEach(item -> {
                    ArticleCategoryDTO articleCategoryDTO = MapUtils.getObject(categoryMap, item.getArticleCategoryId());
                    if (Objects.nonNull(articleCategoryDTO)) {
                        item.setCover(articleCategoryDTO.getCover());
                        item.setArticleCategoryName(articleCategoryDTO.getArticleCategoryName());
                    }
                });
                articleDTO1.setArticleCategoryList(articleExt.getArticleCategoryList());
            }
        }
    }

    /**
     * 文章分页列表
     * <p>
     * 由于需要按点击量排序，所以需要缓存
     * <p>
     * 阅读量 来排序
     *
     * @param req
     * @return
     */
    public PageResult<ArticleDTO> listPage(ArticleListReq req) {
        buildExtParam(req);
        // 远程调用
        PageResult<ArticleDTO> pageResult = remoteArticleService.listPage(req);
        List<ArticleDTO> records = pageResult.getRows();
        if (CollUtil.isEmpty(records)) {
            return pageResult;
        }
        // 用户行为数据
        actionDataManager.buildUserActionData(records, SecurityUtils.getUserId());
        return pageResult;
    }

    /**
     * 构建扩展参数
     *
     * @param req
     */
    private void buildExtParam(ArticleListReq req) {
        try {
            // 增加国家控制 -- 根据IP 获取国家，
            String isoCountryCode = ip2Location.getISOCountryCode();
            if (articleConfig.findSmallMouthCountry(isoCountryCode)) {
                req.setArticleLabel(ArticleLabelEnum.SMALL_MOUTH.getCode());
            }
        } catch (Exception e) {
            log.error("------------{}", e);
        }
    }

    /**
     * 个人最新发布文章列表
     *
     * @return
     */
    public List<ArticleDTO> ownerLatest() {
        // 远程调用
        R<List<ArticleDTO>> result = remoteArticleService.ownerLatest();
        if (Objects.isNull(result) || CollUtil.isEmpty(result.getData())) {
            return Collections.emptyList();
        }
        List<ArticleDTO> records = result.getData();
        // 用户行为数据
        actionDataManager.buildUserActionData(records, SecurityUtils.getUserId());
        return records;
    }

    public PageResult<ArticleDTO> kolListPage(KolListReq req) {
        // 远程调用
        PageResult<ArticleDTO> pageResult = remoteArticleService.kolListPage(req);
        List<ArticleDTO> records = pageResult.getRows();
        if (CollUtil.isEmpty(records)) {
            return pageResult;
        }
        // 用户行为数据
        actionDataManager.buildUserActionData(records, SecurityUtils.getUserId());
        return pageResult;
    }

    public PageResult<ArticleDTO> statuteListPage(StatuteListReq req) {
        if (Objects.nonNull(req.getPublishEndTime()) && Objects.nonNull(req.getPublishStartTime())
                && req.getPublishStartTime().getTime() > req.getPublishEndTime().getTime()
        ) {
            throw new ServiceException("Query start time greater than end time");
        }
        // 远程调用
        PageResult<ArticleDTO> pageResult = remoteArticleService.statuteListPage(req);
        List<ArticleDTO> records = pageResult.getRows();
        if (CollUtil.isEmpty(records)) {
            return pageResult;
        }
        // 用户行为数据
        actionDataManager.buildUserActionData(records, SecurityUtils.getUserId());
        return pageResult;
    }

    @TenantFilter
    public IPage<ArticleDTO> ownerListPage(ArticleOwnerListReq req) {
        return ownerArticleQueryList(req);
    }

    private IPage<ArticleDTO> ownerArticleQueryList(ArticleOwnerListReq req) {

        Page<ArticleDTO> customerPage = new Page<>(req.getPageNum(), req.getPageSize());
        if (Objects.isNull(req.getUserId()) || req.getUserId() <= 0) {
            log.error("user id is empty");
            return new Page<>(req.getPageNum(), req.getPageSize(), 0);
        }
        req.setTenantId(tenantFilterProperties.getTenantByBrand(SecurityUtils.getBrand()));
        IPage<ArticleDTO> page = articleService.ownerList(customerPage, req);
        List<ArticleDTO> articleDTOList = page.getRecords();
        if (CollUtil.isEmpty(articleDTOList)) {
            return page;
        }
        // 填充用户行为数据
        actionDataManager.buildUserActionData(articleDTOList, req.getUserId());
        return page;
    }

    @TenantFilter
    public IPage<ArticleDTO> brandSpecialPage(ArticleBrandSpecialListReq req) {
        Integer tenantId = tenantFilterProperties.getTenantByBrand(req.getBrandNo());
        if (Objects.isNull(tenantId)) {
            return new Page<>(req.getPageNum(), req.getPageSize(), 0);
        }
        Page<ArticleDTO> customerPage = new Page<>(req.getPageNum(), req.getPageSize());
        req.setAuditStatus(ArticleAuditStatusEnum.APPROVED.getCode());
        req.setTenantId(tenantId);
        req.setSource(ArticleSourceEnum.BACKEND.getCode());
        IPage<ArticleDTO> page = articleService.brandSpecialPage(customerPage, req);
        List<ArticleDTO> articleDTOList = page.getRecords();
        if (CollUtil.isEmpty(articleDTOList)) {
            return page;
        }
        // 填充用户行为数据
        actionDataManager.buildUserActionData(articleDTOList, req.getUserId());
        return page;
    }

    public IPage<ArticleGetLikesDTO> getLikeListPage(GetLikesListReq req) {
        Page<ArticleGetLikesDTO> customerPage = new Page<>(req.getPageNum(), req.getPageSize());
        if (Objects.isNull(req.getUserId()) || req.getUserId() <= 0) {
            log.error("user id is empty");
            return new Page<>(req.getPageNum(), req.getPageSize(), 0);
        }
        IPage<ArticleGetLikesDTO> page = articleService.getLikeList(customerPage, req.getUserId());
        List<ArticleGetLikesDTO> actionLikesList = page.getRecords();
        if (CollUtil.isEmpty(actionLikesList)) {
            return page;
        }
        // 点赞人头像
        actionLikesList.forEach(actionItem -> {
            actionItem.setAvatar(userCacheService.getSignUserAvatar(actionItem.getCreatedBy()));
            actionItem.setNickName(userCacheService.getSignUserNickName(actionItem.getCreatedBy()));
            checkUserActions(actionItem, SecurityUtils.getUserId(), req.getUserId());
        });
        return page;
    }

    private void checkUserActions(ArticleGetLikesDTO item, Long userId, Long inputUserId) {
        if (userId.equals(inputUserId)) {
            item.setHasOwnerLikes(true);
            return;
        }
        item.setHasOwnerLikes(userActionCacheService.getOwnerAction(userId, item.getArticleId(),
                MyStatisticsTypeEnum.LIKES_ARTICLE.getBusinessType()));
    }

    /**
     * 文章审核列表查询
     *
     * @param req
     * @return
     */
    @TenantFilter
    public IPage<ArticleDTO> auditList(ArticleAuditListReq req) {
        Page<ArticleDTO> customerPage = new Page<>(req.getPageNum(), req.getPageSize());
        Integer tenantId = tenantFilterProperties.getTenantByBrand(req.getBrandNo());
        req.setTenantId(tenantId);
        // 1、查询文章
        IPage<ArticleDTO> activityDTOIPage = articleService.articleList(customerPage, req);
        List<ArticleDTO> records = activityDTOIPage.getRecords();
        if (CollUtil.isEmpty(records)) {
            return activityDTOIPage;
        }
        List<Long> articleIds = records.stream().map(ArticleDTO::getArticleId).collect(Collectors.toList());
        //  状态 0-正常 1-屏蔽 2-举报
        buildStatus(records, articleIds);
        // 文章状态,审核状态等描述字段
        AtomicInteger rowNum = new AtomicInteger(1);
        activityDTOIPage.getRecords().forEach(item -> {
            item.setSeqNo((req.getPageNum() - 1) * req.getPageSize() + rowNum.get());
            rowNum.getAndIncrement();
            buildContentType(item);
            buildDesc(item);
        });
        // 回复数， 点赞数 设值
        actionDataManager.processArticleData(records);
        // 设置点击数
        buildViewCount(records, articleIds);
        // 文章类别数据查询
        buildArticleExt(records, articleIds);
        // 填充曝光统计数据
        buildExposureCounts(records, articleIds);
        // 设置品牌字段
        records.forEach(item -> item.setBrandNo(tenantFilterProperties.getBrandByTenantId(item.getTenantId())));
        return activityDTOIPage;
    }

    private void buildExposureCounts(List<ArticleDTO> records, List<Long> articleIds) {
        List<ArticleExposureCount> exposureCounts = exposureCountService.countByArticleId(articleIds);
        Map<Long, ArticleExposureCount> exposureMap = exposureCounts.stream().collect(Collectors.toMap(ArticleExposureCount::getObjectId, Function.identity(), (k1, k2) -> k1));
        records.forEach(
                item -> {
                    ArticleExposureCount articleExtItem = MapUtils.getObject(exposureMap, item.getArticleId());
                    item.setExposureCount(Objects.isNull(articleExtItem) ? 0L : articleExtItem.getExposureCount());
                }
        );
    }

    private void buildArticleExt(List<ArticleDTO> records, List<Long> articleIds) {
        List<ArticleExt> extList = articleExtService.list(new LambdaQueryWrapper<ArticleExt>()
                .in(ArticleExt::getArticleId, articleIds)
        );
        Map<Long, ArticleExt> extMap = extList.stream().collect(Collectors.toMap(ArticleExt::getArticleId, Function.identity(), (k1, k2) -> k1));
        records.forEach(
                item -> {
                    ArticleExt articleExtItem = MapUtils.getObject(extMap, item.getArticleId(), new ArticleExt());
                    item.setArticleCategoryList(articleExtItem.getArticleCategoryList());
                }
        );
    }

    private void buildViewCount(List<ArticleDTO> records, List<Long> articleIds) {
        Map<Long, Long> articleViewsMap = articleViewsService.countArticleViewsByArticleId(articleIds);
        records.forEach(item -> item.setViewCount(MapUtils.getObject(articleViewsMap, item.getArticleId(), 0L)));
    }

    private void buildViewCount(ArticleDTO articleDTO) {
        List<Long> articleIds = Lists.newArrayList();
        articleIds.add(articleDTO.getArticleId());
        Map<Long, Long> articleViewsMap = articleViewsService.countArticleViewsByArticleId(articleIds);
        articleDTO.setViewCount(MapUtils.getObject(articleViewsMap, articleDTO.getArticleId(), 0L));
    }

    private void buildStatus(List<ArticleDTO> list, List<Long> articleIds) {
        // 屏蔽 状态设值  CONTENT_STATUS_SHIELD
        list.forEach(evaluateItem -> evaluateItem.setStatus(evaluateItem.getHasBlocked()));
        List<UserReport> userReports = reportService.list(new LambdaQueryWrapper<UserReport>()
                .eq(UserReport::getBusinessType, ReportConstant.ARTICLE_TYPE)
                .eq(UserReport::getDeletedVersion, 0)
                .in(UserReport::getRelationId, articleIds)
        );
        // 举报 状态设值  CONTENT_STATUS_REPORT
        if (CollUtil.isNotEmpty(userReports)) {
            userReports.forEach(item -> {
                ArticleDTO articleDTO = list.stream().filter(evaluateItem -> evaluateItem.getArticleId().equals(item.getRelationId())).findFirst().orElse(null);
                Optional.ofNullable(articleDTO).ifPresent(evaluateDTOItem -> evaluateDTOItem.setStatus(CONTENT_STATUS_REPORT));
            });
        }
    }

    private void buildContentType(ArticleDTO item) {
        ArticleContentTypeEnum typeEnum = ArticleContentTypeEnum.getNameByCode(item.getContentType());
        item.setContentTypeDesc(Objects.isNull(typeEnum) ? "" : typeEnum.getName());
    }

    private void buildDesc(ArticleDTO item) {
        ArticleAuditStatusEnum typeEnum = ArticleAuditStatusEnum.getNameByCode(item.getAuditStatus());
        item.setAuditStatusDesc(Objects.isNull(typeEnum) ? "" : typeEnum.getName());
        ArticleStatusEnum typeEnum2 = ArticleStatusEnum.getNameByCode(item.getStatus());
        item.setStatusDesc(Objects.isNull(typeEnum2) ? "" : typeEnum2.getName());
        ArticleLabelEnum typeEnum1 = ArticleLabelEnum.getNameByCode(item.getArticleLabel());
        item.setArticleLabelDesc(Objects.isNull(typeEnum1) ? "" : typeEnum1.getName());
    }

    public Boolean auditArticle(ArticleAuditReq req) {
        Article article = articleService.getOne(new LambdaQueryWrapper<Article>()
                .select(Article::getArticleId, Article::getAuditStatus
                        , Article::getTenantId, Article::getSource, Article::getContentType
                        , Article::getCreatedBy)
                .eq(Article::getArticleId, req.getArticleId())
                .eq(Article::getDeletedVersion, 0L)
        );
        if (Objects.isNull(article)) {
            throw new ServiceException("article is empty");
        }
        // 只有IN_REVIEW状态的文章才可进行审核
        if (!ArticleAuditStatusEnum.IN_REVIEW.getCode().equals(article.getAuditStatus())) {
            throw new ServiceException("article had been approved or draft");
        }
        // 1-状态转换  (不严谨)
        boolean flag = articleService.update(new LambdaUpdateWrapper<Article>()
                .set(Article::getAuditStatus, req.getAuditStatus())
                .set(Article::getUpdatedBy, SecurityUtils.getUserId())
                .set(Article::getUpdatedTime, new Date())
                .eq(Article::getArticleId, req.getArticleId())
                .eq(Article::getDeletedVersion, 0L)
        );
        // 3-生成审核日志
        boolean reviewFlag = ArticleAuditStatusEnum.APPROVED.getCode().equals(req.getAuditStatus());
        if (Boolean.TRUE.equals(flag)) {
            // 文章审核通过
            articleCacheService.clearOwnerCache(article.getCreatedBy());
            // 2-监控后台发布文章数量
            publisher.publishEvent(new AuditEvent(req.getAuditStatus(), article.getTenantId(), article.getSource(), article.getContentType()));
            ArticleAuditLog log = ArticleAuditLog.builder()
                    .articleId(req.getArticleId())
                    .remark(reviewFlag ? ArticleAuditStatusEnum.APPROVED.getNameEn()
                            : ArticleAuditStatusEnum.REVIEW_FAILED.getNameEn() + " : " + req.getRemark())
                    .createdTime(new Date())
                    .createdBy(SecurityUtils.getUserId())
                    .createdName(SecurityUtils.getUsername())
                    .build();
            auditLogService.save(log);
        }
        return flag;
    }

    /**
     * 文章首页发帖积分处理
     *
     * @param article
     */
    private void processPoints(Article article, List<TopicObjVO> topicList, UserPointResultVO pointResultVO) {
        if (ArticleAuditStatusEnum.DRAFT.getCode().equals(article.getAuditStatus())) {
            log.warn("article audit status is in draft, skip points process");
            return;
        }
        // 首页文章发布
        IPointsDetailStrategy pointsDetailStrategy = pointsDetailStrategyMap.get(PointRuleConstant.ARTICLE_PUBLISH_HOME + IPointsDetailStrategy.SUFFIX);
        boolean pointResult = pointsDetailStrategy.updatePoints(article.getArticleId(), SecurityUtils.getUserId());
        if (Boolean.TRUE.equals(pointResult)) {
            pointResultVO.setPointsDetailValue(ruleConfigCacheService.getConfigByCode(PointRuleConstant.ARTICLE_PUBLISH_HOME).getPointsValue());
            pointResultVO.setGetPointsFlag(true);
        }
        // 发布带topic的积分处理
        processTopicPoint(article, topicList, pointResultVO);
    }

    /**
     * 处理带topic的积分处理
     * @param article
     * @param topicList
     */
    private void processTopicPoint(Article article, List<TopicObjVO> topicList, UserPointResultVO pointResultVO) {
        IPointsDetailStrategy pointsDetailStrategy;
        // 这里要增加判定逻辑 ，如果 引用了指定的topic 才有积分
        Integer count = getTopicNumber(topicList);
        if (Objects.nonNull(count) && count > 0) {
            // 发帖带指定topic
            pointsDetailStrategy = pointsDetailStrategyMap.get(PointRuleConstant.ARTICLE_APPOINT_TOPIC + IPointsDetailStrategy.SUFFIX);
            boolean pointResult = pointsDetailStrategy.updatePoints(article.getArticleId(), SecurityUtils.getUserId());
            if (Boolean.TRUE.equals(pointResult)) {
                pointResultVO.setPointsDetailValue(ruleConfigCacheService.getConfigByCode(PointRuleConstant.ARTICLE_APPOINT_TOPIC).getPointsValue());
                pointResultVO.setGetPointsFlag(true);
            }
        }
    }

    private Integer getTopicNumber(List<TopicObjVO> topicList) {
        Integer count = 0;
        if (CollUtil.isNotEmpty(topicList)) {
            count = articleTagService.lambdaQuery()
                    .in(ArticleTag::getArticleId, topicList.stream().map(TopicObjVO::getTopicId).collect(Collectors.toList()))
                    .eq(ArticleTag::getTagEnable, TagStatus.ENABLED.getCode())
                    .eq(ArticleTag::getDeletedVersion, 0L)
                    .count();
        }
        return count;
    }

    @Autowired
    private IStoreService storeService;

    @TenantFilter
    public IPage<ArticleAssociateResp> associateList(ArticleAssociateListReq req) {
        if (CharSequenceUtil.isBlank(req.getHeadline())) {
            return new Page<>(req.getPageNum(), req.getPageSize(), 0);
        }
        if (CharSequenceUtil.isNotBlank(req.getBusinessCode())) {
            SearchBusinessEnum businessEnum = SearchBusinessEnum.getByCode(req.getBusinessCode());
            if (Objects.isNull(businessEnum)) {
                throw new ServiceException("businessCode is error");
            }
            if (SearchBusinessEnum.ARTICLE.equals(businessEnum)) {
                req.setTenantId(tenantFilterProperties.getTenantByBrand(SecurityUtils.getBrand()));
                return articleCacheService.associatePageCache(req);
            } else if (SearchBusinessEnum.STORE.equals(businessEnum)) {
                return storeService.getStoreList(new Page<>(req.getPageNum(), req.getPageSize()) ,req);
            }
        }
        return new Page<>(req.getPageNum(), req.getPageSize(), 0);
    }

    public Boolean saveWeight(Long articleId, Integer weight) {
        // 判定文章是否审核通过
        Article article = articleService.getOne(new LambdaQueryWrapper<Article>()
                .select(Article::getArticleId, Article::getWeight, Article::getHasBlocked, Article::getCreatedBy)
                .eq(Article::getArticleId, articleId)
                .eq(Article::getAuditStatus, ArticleAuditStatusEnum.APPROVED.getCode())
                .eq(Article::getDeletedVersion, 0L)
        );
        if (Objects.isNull(article) || article.getHasBlocked() > 0) {
            throw new ServiceException("Audit Status need to be approved or blocked");
        }
        boolean flag = articleService.update(new LambdaUpdateWrapper<Article>()
                .set(Article::getWeight, weight)
                .set(Article::getUpdatedTime, new Date())
                .set(Article::getUpdatedBy, SecurityUtils.getUserId())
                .eq(Article::getWeight, article.getWeight())
                .eq(Article::getArticleId, articleId)
                .eq(Article::getDeletedVersion, 0L)
        );
        // 发帖带指定topic
        IPointsDetailStrategy pointsDetailStrategy = pointsDetailStrategyMap.get(PointRuleConstant.ARTICLE_TOP_UP + IPointsDetailStrategy.SUFFIX);
        pointsDetailStrategy.updatePoints(article.getArticleId(), article.getCreatedBy());
        // 清除缓存
        articleCacheService.clearCache();
        return flag;
    }

    public Boolean articleLabelSetting(Long articleId, Integer label) {
        boolean flag = articleService.update(new LambdaUpdateWrapper<Article>()
                .set(Article::getArticleLabel, label)
                .set(Article::getUpdatedTime, new Date())
                .set(Article::getUpdatedBy, SecurityUtils.getUserId())
                .eq(Article::getArticleId, articleId)
                .eq(Article::getDeletedVersion, 0L)
        );
        // 清除缓存
        articleCacheService.clearCache();
        return flag;
    }

    public Boolean saveKolSetting(Long articleId, Integer hasKol) {
        // 判定文章是否审核通过
        Article article = articleService.getOne(new LambdaQueryWrapper<Article>()
                .select(Article::getArticleId, Article::getHasKol, Article::getHasBlocked)
                .eq(Article::getArticleId, articleId)
                .eq(Article::getAuditStatus, ArticleAuditStatusEnum.APPROVED.getCode())
                .eq(Article::getDeletedVersion, 0L)
        );
        if (Objects.isNull(article) || article.getHasBlocked() > 0) {
            throw new ServiceException("Audit Status need to be approved or blocked");
        }
        boolean flag = articleService.update(new LambdaUpdateWrapper<Article>()
                .set(Article::getHasKol, hasKol)
                .set(Article::getUpdatedTime, new Date())
                .set(Article::getUpdatedBy, SecurityUtils.getUserId())
                .eq(Article::getHasKol, article.getHasKol())
                .eq(Article::getArticleId, articleId)
                .eq(Article::getDeletedVersion, 0L)
        );
        // 清除缓存
        articleCacheService.clearCache();
        return flag;
    }


}
